﻿using System;
using System.Collections.Generic;
using System.Text;
using VIX.Enums;
using VixCOM;

namespace VIX
{
    /// <summary>
    /// Represents a VMware IHost.
    /// </summary>
    public class Host : IDisposable
    {
        #region Private Fields
        private IHost hostHandle = null;
        private IHost2 hostHandle2 = null;
        private IHost3 hostHandle3 = null;
        private bool connected;
        #endregion

        #region Constructor/Destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="Host"/> class. Pass the IHost handle referencing the host as a parameter.
        /// </summary>
        /// <param name="handle">The IHost handle to the Virtual Machine Host.</param>
        internal Host(IHost handle)
        {
            this.hostHandle = handle;
            this.hostHandle2 = this.hostHandle as IHost2;
            this.hostHandle3 = this.hostHandle2 as IHost3;
            this.connected = true;
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="Host"/> class.
        /// </summary>
        ~Host()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (connected)
                this.Disconnect();

            this.hostHandle = null;
            this.hostHandle2 = null;
            this.hostHandle3 = null;

            GC.SuppressFinalize(this);
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the underlying VIX-handle
        /// </summary>
        internal IHost Handle
        {
            get { return this.hostHandle; }
        }

        /// <summary>
        /// Gets the underlying VIX-handle
        /// </summary>
        internal IHost2 Handle2
        {
            get { return this.hostHandle2; }
        }

        /// <summary>
        /// Gets the underlying VIX-handle
        /// </summary>
        internal IHost3 Handle3
        {
            get { return this.hostHandle3; }
        }
        #endregion

        #region VIX-API functions
        /// <summary>
        /// Call this function to disconnect the host.
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>Destroys the state for a particular host.</item>
        /// <item>After you call this function the object is no longer valid and you should not use it in any Vix function. Similarly, you should not use any objects obtained from the host while it was connected.</item>
        /// <item>Disconnect the host before you destroy the object.</item>
        /// <item>Before calling Disconnect, you must call VM.Dispose() on each open VM.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        public void Disconnect()
        {
            this.hostHandle.Disconnect();
            this.connected = false;
        }

        /// <summary>
        /// Retrieves information about the host. For example, when used to find all running virtual machines, it returns a series of virtual machine file path names. 
        /// </summary>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="searchType">The type of items to find. Check <see cref="VIX_FIND"/> for values.</param>
        /// <returns>A <see cref="System.Collections.Generic.List{T}"/> of strings that contains all results found.</returns>
        public List<string> FindItems(VIX_FIND searchType)
        {
            IJob jobHandle = this.hostHandle.FindItems((int)searchType, null, -1, null);
            JobWaitWithoutResults(jobHandle);

            List<object> result = new List<object>();
            result.AddRange((object[])(VixCOMWrapper.Instance.GetProperties((IVixHandle)jobHandle, new VIX_PROPERTY[] { VIX_PROPERTY.FOUND_ITEM_LOCATION })[0]));

            // At the time of the implementation, FindItems() only retrieves the path of running or registered VMs, which are strings only
            return result.ConvertAll(value => (string)value);
        }

        /// <summary>
        /// This method allows you to get one or more properties from the host. For a list of property IDs, see <see cref="VIX_PROPERTY"/>.
        /// </summary>
        /// <remarks>
        /// <list type="bullet"> 
        /// <item>This function allows you to get one or more properties from the host, but only specific properties may be defined for this object.</item>
        /// <item>The value of <see cref="VIX_PROPERTY.VM_TOOLS_STATE"/> is valid only after calling <see cref="VM.WaitForToolsInGuest"/>.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="propertyIDs">An array of property-IDs to identify the properties to fetch.</param>
        /// <returns>A <see cref="System.Collections.Generic.List{T}"/> object that stores the properties identified by the array <paramref name="propertyIDs"/>. Each element in <paramref name="propertyIDs"/> matches the corresponding indentifier in the returned <see cref="System.Collections.Generic.List{T}"/>; for example, if the first element in the <paramref name="propertyIDs"/> array is <see cref="VIX_PROPERTY.HOST_HOSTTYPE"/>, the first element in the <see cref="System.Collections.Generic.List{T}"/> will be the object that is the result of this job, if one exists.</returns>
        public List<object> GetProperties(VIX_PROPERTY[] propertyIDs)
        {
            return VixCOMWrapper.Instance.GetProperties((IVixHandle)this.hostHandle, propertyIDs);
        }

        /// <summary>
        /// This function opens a virtual machine on the host that is identified by the <paramref name="vmxFilePathName"/> parameter and returns a <see cref="VM"/> object representing a virtual machine. 
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>The virtual machine is identified by <paramref name="vmxFilePathName"/>, which is a path name to the configuration file (.VMX file) for that virtual machine.</item>
        /// <item>The format of the path name depends on the host operating system. For example, a path name for a Windows host requires backslash as a directory separator, whereas a Linux host requires a forward slash. If the path name includes backslash characters, you need to precede each one with an escape character. For VMware Server 2.x, the path contains a preceeding data store, for example [storage1] vm/vm.vmx.</item>
        /// <item>For VMware Server hosts, a virtual machine must be registered before you can open it. You can register a virtual machine by opening it with the VMware Server Console, through the vmware-cmd command with the register parameter, or with <see cref="RegisterVM"/>.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="vmxFilePathName">The path name of the virtual machine configuration file on the local host.</param>
        /// <returns>A <see cref="VM"/> object representing the opened VM.</returns>
        [Obsolete("This function is superseded by OpenVMEx.")]
        public VM OpenVM(string vmxFilePathName)
        {
            IJob jobHandle = this.hostHandle.OpenVM(vmxFilePathName, null);

            object foundProperties = null;

            ulong err = jobHandle.Wait(new VIX_PROPERTY[] { VIX_PROPERTY.JOB_RESULT_HANDLE }, ref foundProperties);
            VixCOMWrapper.Instance.CheckForError(err);

            IVM vmH = (IVM)((object[])foundProperties)[0];
            return new VM(vmH);
        }

        /// <summary>
        /// This function opens a virtual machine on the host that is identified by the <paramref name="vmxFilePathName"/> parameter and returns a <see cref="VM"/> object representing a virtual machine. This function supercedes OpenVM.
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>The virtual machine is identified by <paramref name="vmxFilePathName"/>, which is a path name to the configuration file (.VMX file) for that virtual machine.</item>
        /// <item>The format of the path name depends on the host operating system. For example, a path name for a Windows host requires backslash as a directory separator, whereas a Linux host requires a forward slash. If the path name includes backslash characters, you need to precede each one with an escape character. For VMware Server 2.x, the path contains a preceeding data store, for example [storage1] vm/vm.vmx.</item>
        /// <item>For VMware Server hosts, a virtual machine must be registered before you can open it. You can register a virtual machine by opening it with the VMware Server Console, through the vmware-cmd command with the register parameter, or with <see cref="RegisterVM"/>.</item>
        /// <item>To open an encrypted virtual machine, pass a handle to a property list containing the property VIX_PROPERTY_VM_ENCRYPTION_PASSWORD set to the password for the virtual machine.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="vmxFilePathName">The path name of the virtual machine configuration file on the local host.</param>
        /// <param name="propertyList">A property list containing extra information that might be needed to open the VM. You can pass null if no extra information is needed.</param>        
        /// <returns>A <see cref="VM"/> object representing the opened VM.</returns>
        public VM OpenVMEx(string vmxFilePathName, PropertyList propertyList)
        {
            IJob jobHandle = this.hostHandle2.OpenVMEx(vmxFilePathName, (int)VixCOM.Constants.VIX_VMOPEN_NORMAL, propertyList != null ? propertyList.Handle : null, null);

            object foundProperties = null;

            ulong err = jobHandle.Wait(new VIX_PROPERTY[] { VIX_PROPERTY.JOB_RESULT_HANDLE }, ref foundProperties);
            VixCOMWrapper.Instance.CheckForError(err);

            IVM vmH = (IVM)((object[])foundProperties)[0];
            return new VM(vmH);
        }

        /// <summary>
        /// This method adds a virtual machine to the host's inventory.
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>This method registers the virtual machine identified by <paramref name="vmxFilePathName"/>, which is a storage path to the configuration file (.vmx) for that virtual machine. You can register a virtual machine regardless of its power state.</item>
        /// <item>The format of the path name depends on the host operating system. Path to storage [standard] or [storage1] may vary.</item>
        /// <item>For VMware Server 1.x, supply the full path name instead of storage path, and specify <see cref="VIX_SERVICEPROVIDER.VMWARE_SERVER"/> to connect.</item>
        /// <item>This method returns but has no effect on Workstation or Player, which lack a virtual machine inventory.</item>
        /// <item>It is not a Vix error to register an already-registered virtual machine, although the VMware Server UI shows an error icon in the Task pane. Trying to register a non-existent virtual machine results in an exception with error-code <see cref="VIX_E.NOT_FOUND"/>.</item>
        /// <item>With vSphere, this request can generate an error in which will be displayed on the vSphere client as it tries to find the appropriate resource pool for the Virtual Machine being registered.</item>
        /// <item>With vSphere, this request will register the Virtual Machine in the first matching datastore it finds. If vSphere is configured with more than one Datacenter which contains the same Datastore name, the registration may not occur where it is expected.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="vmxFilePathName">The path name of the .vmx file on the host.</param>
        public void RegisterVM(string vmxFilePathName)
        {
            IJob jobHandle = this.hostHandle.RegisterVM(vmxFilePathName, null);
            JobWaitWithoutResults(jobHandle);
        }

        /// <summary>
        /// This method removes a virtual machine from the host's inventory. 
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>This method unregisters the virtual machine identified by <paramref name="vmxFilePathName"/>, which is a storage path to the configuration file (.vmx) for that virtual machine. A virtual machine must be powered off to unregister it.</item>
        /// <item>The format of the storage path depends on the host operating system. Path to storage [standard] or [storage1] may vary.</item>
        /// <item>For VMware Server 1.x, supply the full path name instead of storage path, and specify <see cref="VIX_SERVICEPROVIDER.VMWARE_SERVER"/> to connect.</item>
        /// <item>This method returns but has no effect on Workstation or Player, which lack a virtual machine inventory.</item>
        /// <item>It is not a Vix error to unregister an already-unregistered virtual machine, nor is it a Vix error to unregister a non-existent virtual machine.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="vmxFilePathName">The path name of the .vmx file on the host.</param>
        public void UnregisterVM(string vmxFilePathName)
        {
            IJob jobHandle = this.hostHandle.UnregisterVM(vmxFilePathName, null);
            JobWaitWithoutResults(jobHandle);
        }

        /// <summary>
        /// Creates a property list. Property lists are useful for passing extra parameters to functions such as host.OpenVMEx.
        /// See also: <see cref="PropertyList.GetProperty"/> and <see cref="PropertyList.SetProperty"/>.
        /// </summary>
        /// <returns>A new <see cref="PropertyList"/> instance that can be used for storing properties.</returns>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        public PropertyList CreatePropertyList()
        {
            if (this.hostHandle3 == null)
                return null;

            IPropertyList propertyList;
            ulong err = this.hostHandle3.CreatePropertyList(out propertyList);

            VixCOMWrapper.Instance.CheckForError(err);

            return new PropertyList(propertyList);
        }
        #endregion

        #region Private functions
        /// <summary>
        /// Waits for the job to complete without checking the results.
        /// </summary>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="jobHandle">An IJob handle to the job.</param>
        private void JobWaitWithoutResults(IJob jobHandle)
        {
            ulong err = jobHandle.WaitWithoutResults();

            VixCOMWrapper.Instance.CheckForError(err);
        }
        #endregion
    }
}
